{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 装饰器和闭包"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数装饰器用于标记函数，以某种方式增强函数的功能。装饰器是基于闭包实现的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 装饰器基础"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器是可调用对象，其参数是被装饰的函数或类。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Running in inner()\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.deco.<locals>.inner()>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def deco(func):\n",
    "    def inner():\n",
    "        print(\"Running in inner()\")\n",
    "    return inner\n",
    "\n",
    "@deco\n",
    "def target():\n",
    "    print(\"running in target()\")\n",
    "    \n",
    "target()\n",
    "target"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器语法糖被翻译为`target = deco(target)`，因此target函数被装饰后实际上变成对inner函数的引用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器在被导入的函数定义时立即执行，这通常是import语句，也就是Python在加载模块时。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 变量作用域规则"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n"
     ]
    },
    {
     "ename": "UnboundLocalError",
     "evalue": "local variable 'b' referenced before assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mUnboundLocalError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-5-16edb15a27ae>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      6\u001b[0m     \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m10\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mfunc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-5-16edb15a27ae>\u001b[0m in \u001b[0;36mfunc1\u001b[0;34m(a)\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m     \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m10\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'b' referenced before assignment"
     ]
    }
   ],
   "source": [
    "b = 6\n",
    "\n",
    "def func1(a):\n",
    "    print(a)\n",
    "    print(b)\n",
    "    b = 10\n",
    "\n",
    "func1(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里b被识别为局部变量了，因为在函数体的定义中给他赋值了。执行`print(b)`时局部变量b还未被赋值，所以报了局部变量未绑定的错误。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果想把`b`当做全局变量使用，必须在使用前加上`global b`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n",
      "6\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b = 6\n",
    "\n",
    "def func2(a):\n",
    "    print(a)\n",
    "    global b\n",
    "    print(b)\n",
    "    b = 10\n",
    "\n",
    "func2(2)\n",
    "b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们用dis模块把func1和func2反汇编一下。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  4           0 LOAD_GLOBAL              0 (print)\n",
      "              2 LOAD_FAST                0 (a)\n",
      "              4 CALL_FUNCTION            1\n",
      "              6 POP_TOP\n",
      "\n",
      "  5           8 LOAD_GLOBAL              0 (print)\n",
      "             10 LOAD_FAST                1 (b)\n",
      "             12 CALL_FUNCTION            1\n",
      "             14 POP_TOP\n",
      "\n",
      "  6          16 LOAD_CONST               1 (10)\n",
      "             18 STORE_FAST               1 (b)\n",
      "             20 LOAD_CONST               0 (None)\n",
      "             22 RETURN_VALUE\n"
     ]
    }
   ],
   "source": [
    "from dis import dis\n",
    "\n",
    "dis(func1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里`LOAD_FAST(b)`就是将本地变量b压入堆栈。\n",
    "\n",
    "我们再看下func2："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  4           0 LOAD_GLOBAL              0 (print)\n",
      "              2 LOAD_FAST                0 (a)\n",
      "              4 CALL_FUNCTION            1\n",
      "              6 POP_TOP\n",
      "\n",
      "  6           8 LOAD_GLOBAL              0 (print)\n",
      "             10 LOAD_GLOBAL              1 (b)\n",
      "             12 CALL_FUNCTION            1\n",
      "             14 POP_TOP\n",
      "\n",
      "  7          16 LOAD_CONST               1 (10)\n",
      "             18 STORE_GLOBAL             1 (b)\n",
      "             20 LOAD_CONST               0 (None)\n",
      "             22 RETURN_VALUE\n"
     ]
    }
   ],
   "source": [
    "dis(func2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里指令变成了`LOAD_GLOBAL(b)`，很明显b被按照全局变量处理。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 闭包"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "刚才讲解了一下装饰器和变量作用域等基本知识，现在我们深入装饰器的原理：闭包"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "闭包是延伸了作用域的函数，它包含了在函数定义体中引用，但是不在定义体中定义的非全局变量。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "听起来比较拗口，我们用一个例子来说明。\n",
    "\n",
    "假如有个名为avg的函数，它的作用是计算不断增加的系列值的均值，比如某只股票每天收盘价的平均值。\n",
    "\n",
    "由于每天都会增加新价格，所以平均价要考虑截止目前的所有历史价格。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10.0\n",
      "10.5\n",
      "11.0\n"
     ]
    }
   ],
   "source": [
    "def make_averager():\n",
    "    series = []\n",
    "    \n",
    "    def averager(new_value):\n",
    "        series.append(new_value)\n",
    "        return sum(series)/len(series)\n",
    "    \n",
    "    return averager\n",
    "\n",
    "avg = make_averager()\n",
    "print(avg(10))\n",
    "print(avg(11))\n",
    "print(avg(12))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`series`是`make_averager`函数的局部变量，当调用`avg(10)`时，`make_averager`函数已经返回了，它的本地作用域也一去不复返了。\n",
    "\n",
    "`make_averager`函数的返回值比较有意思，返回了一个嵌套函数`averager`，并且`averager`函数的定义中引用了不在它的函数体中定义的非全局变量`series`。\n",
    "\n",
    "在`averager`函数中，我们称`series`为自由变量，指不在本地作用域中绑定的变量。结合闭包的定义，我们知道`averager`函数便是一个闭包。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "综上，闭包是一种函数，它会保留定义函数时存在的自由变量的绑定，这样当调用函数时，虽然定义自由变量的作用域不可用了，但是仍然能使用自由变量的绑定。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用闭包时要注意一点，不能对自由变量赋值，因为对变量赋值时实际上会创建局部变量，因此它就不是自由变量了。\n",
    "\n",
    "我们改良一下`make_averager`函数，记住目前为止所有元素的加和以及元素个数，这样就不用重复计算历史数据了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "ename": "UnboundLocalError",
     "evalue": "local variable 'total' referenced before assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mUnboundLocalError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-11-53487d68b7fe>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m     11\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     12\u001b[0m \u001b[0mavg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmake_averager1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0mavg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-11-53487d68b7fe>\u001b[0m in \u001b[0;36maverager\u001b[0;34m(new_value)\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0maverager\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnew_value\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m         \u001b[0mtotal\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mnew_value\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      7\u001b[0m         \u001b[0mcount\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      8\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0mtotal\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mcount\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'total' referenced before assignment"
     ]
    }
   ],
   "source": [
    "def make_averager1():\n",
    "    total = 0\n",
    "    count = 0\n",
    "    \n",
    "    def averager(new_value):\n",
    "        total += new_value\n",
    "        count += 1\n",
    "        return total/count\n",
    "    \n",
    "    return averager\n",
    "\n",
    "avg = make_averager1()\n",
    "avg(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里报错了，因为对`total += new_value`实际上是`total = total + new_value`，等号右边对total访问时它还没有绑定任何值。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为解决这个问题，Python3引入了新的关键字`nonlocal`。它的作用是把变量标记为自由变量，如果为nonlocal声明的变量赋予了新值，闭包中保存的绑定也会更新。\n",
    "\n",
    "最新版`make_averager`正确实现如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10.0\n",
      "10.5\n",
      "11.0\n"
     ]
    }
   ],
   "source": [
    "def make_averager2():\n",
    "    total = 0\n",
    "    count = 0\n",
    "    \n",
    "    def averager(new_value):\n",
    "        nonlocal total, count\n",
    "        total += new_value\n",
    "        count += 1\n",
    "        return total/count\n",
    "    \n",
    "    return averager\n",
    "\n",
    "avg = make_averager2()\n",
    "print(avg(10))\n",
    "print(avg(11))\n",
    "print(avg(12))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 实现简单的装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "实现一个函数计时器，把函数执行时间、入参和返回值打印出来。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "def clock(func):\n",
    "    def clocker(*args):\n",
    "        t0 = time.perf_counter()\n",
    "        result = func(*args)\n",
    "        elapsed = time.perf_counter() - t0\n",
    "        name = func.__name__\n",
    "        arg_str = \",\".join(repr(arg) for arg in args)\n",
    "        print(\"[%0.8fs] %s(%s) -> %r\" % (elapsed, name, arg_str, result))\n",
    "        return result\n",
    "    return clocker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个装饰器用法如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.12515392s] snooze(0.123) -> None\n",
      "[0.00000109s] factorial(1) -> 1\n",
      "[0.00008350s] factorial(2) -> 2\n",
      "[0.00013029s] factorial(3) -> 6\n",
      "[0.00016825s] factorial(4) -> 24\n",
      "[0.00019864s] factorial(5) -> 120\n",
      "[0.00023665s] factorial(6) -> 720\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "720"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "@clock\n",
    "def snooze(seconds):\n",
    "    time.sleep(seconds)\n",
    "    \n",
    "@clock\n",
    "def factorial(n):\n",
    "    return 1 if n < 2 else n*factorial(n-1)\n",
    "\n",
    "snooze(0.123)\n",
    "factorial(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 装饰器带参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器本身带参数，使装饰器的功能更灵活，比如flask的路由装饰器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'/index': <function index at 0x7fc198a500e0>, '/home': <function home at 0x7fc198a50050>}\n"
     ]
    }
   ],
   "source": [
    "url_mapping = {}\n",
    "\n",
    "def route(url):\n",
    "    def decorator(func):\n",
    "        url_mapping[url] = func\n",
    "        def inner(*args, **kargs):\n",
    "            return func(*args, **kargs)\n",
    "        return inner\n",
    "    return decorator\n",
    "\n",
    "@route('/index')\n",
    "def index():\n",
    "    pass\n",
    "\n",
    "@route('/home')\n",
    "def home(name):\n",
    "    print(name)\n",
    "\n",
    "print(url_mapping)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 类装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类装饰器有很多种，只介绍2种比较常用的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 用类封装装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器本质上是一个可调用对象，可以实现类的`__call__`方法用来把类封装成一个装饰器。\n",
    "\n",
    "用类实现clock装饰器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.12307208s] snooze(0.123) -> None\n",
      "[0.00000502s] factorial(6) -> 720\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "720"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "class clock_by_class():\n",
    "    def __init__(self, func):\n",
    "        self.func = func\n",
    "    \n",
    "    def __call__(self, *args):\n",
    "        t0 = time.perf_counter()\n",
    "        result = self.func(*args)\n",
    "        elapsed = time.perf_counter() - t0\n",
    "        name = self.func.__name__\n",
    "        arg_str = \",\".join((repr(arg) for arg in args))\n",
    "        print(\"[%0.8fs] %s(%s) -> %r\" % (elapsed, name, arg_str, result))\n",
    "        return result\n",
    "        \n",
    "@clock_by_class\n",
    "def snooze(seconds):\n",
    "    time.sleep(seconds)\n",
    "    \n",
    "@clock_by_class\n",
    "def factorial(n):\n",
    "    total = 1\n",
    "    for i in range(1, n+1):\n",
    "        total = total*i\n",
    "    return total\n",
    "\n",
    "snooze(0.123)\n",
    "factorial(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "调用被装饰的函数，实际上是执行装饰器类的`__call__`方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 类成员的装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类成员装饰器与普通装饰器的区别在于，类成员多了默认的self参数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1.20306088s] snooze(1.2) -> None\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "def clock_for_class_member(func):\n",
    "    def clocker(self, *args):\n",
    "        t0 = time.perf_counter()\n",
    "        result = func(self, *args)\n",
    "        elapsed = time.perf_counter() - t0\n",
    "        name = func.__name__\n",
    "        arg_str = \",\".join(repr(arg) for arg in args)\n",
    "        print(\"[%0.8fs] %s(%s) -> %r\" % (elapsed, name, arg_str, result))\n",
    "        return result\n",
    "    \n",
    "    return clocker\n",
    "\n",
    "class Snooze():\n",
    "    def __init__(self):\n",
    "        pass\n",
    "    \n",
    "    @clock_for_class_member\n",
    "    def snooze(self, seconds):\n",
    "        time.sleep(seconds)\n",
    "\n",
    "s = Snooze()\n",
    "s.snooze(1.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python内置了几个全局的类成员装饰器，比如`property`、`staticmethod`、`classmethod`等。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## functools标准库中的辅助装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "标准库functools中封装了一些比较有用的辅助装饰器。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### functools.wraps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "装饰器实际上替换了原函数，包括函数名称、函数签名等，比如上面例子中的函数名："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.clock.<locals>.clocker(self, *args)>"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Snooze.snooze"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.clock_by_class at 0x7fc198a62550>"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "factorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`functools.wraps`装饰器将原函数的签名完整的拷贝到装饰后的函数中去，一般没有特殊原因都建议给自定义装饰器加上`@functools.wraps(func)`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.index(name)>"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import functools\n",
    "\n",
    "url_mapping = {}\n",
    "\n",
    "def route(url):\n",
    "    def decorator(func):\n",
    "        url_mapping[url] = func\n",
    "        @functools.wraps(func)\n",
    "        def inner(*args, **kargs):\n",
    "            return func(*args, **kargs)\n",
    "        return inner\n",
    "    return decorator\n",
    "\n",
    "@route(\"/index\")\n",
    "def index(name):\n",
    "    print(name)\n",
    "\n",
    "index"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### functools.lru_cache"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`functools.lrucache`是一个非常实用的装饰器，它可以实现记忆功能，把耗时的函数计算结果保存起来，避免传入相同参数时重复计算。\n",
    "\n",
    "lru是Least Recently Used的缩写，lru缓存不会无限制增长，一段时间不用的缓存会被新增的缓存项替换掉。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.00000039s] fib(0) -> 0\n",
      "[0.00000040s] fib(1) -> 1\n",
      "[0.00015354s] fib(2) -> 1\n",
      "[0.00000033s] fib(1) -> 1\n",
      "[0.00000040s] fib(0) -> 0\n",
      "[0.00000040s] fib(1) -> 1\n",
      "[0.00005638s] fib(2) -> 1\n",
      "[0.00018253s] fib(3) -> 2\n",
      "[0.00036831s] fib(4) -> 3\n",
      "[0.00000025s] fib(1) -> 1\n",
      "[0.00000025s] fib(0) -> 0\n",
      "[0.00000024s] fib(1) -> 1\n",
      "[0.00003459s] fib(2) -> 1\n",
      "[0.00009625s] fib(3) -> 2\n",
      "[0.00000032s] fib(0) -> 0\n",
      "[0.00000035s] fib(1) -> 1\n",
      "[0.00003415s] fib(2) -> 1\n",
      "[0.00000023s] fib(1) -> 1\n",
      "[0.00000054s] fib(0) -> 0\n",
      "[0.00000023s] fib(1) -> 1\n",
      "[0.00003083s] fib(2) -> 1\n",
      "[0.00006118s] fib(3) -> 2\n",
      "[0.00012694s] fib(4) -> 3\n",
      "[0.00026216s] fib(5) -> 5\n",
      "[0.00066276s] fib(6) -> 8\n",
      "8\n"
     ]
    }
   ],
   "source": [
    "@clock\n",
    "def fib(n):\n",
    "    if n < 2:\n",
    "        return n\n",
    "    return fib(n-2) + fib(n-1)\n",
    "\n",
    "print(fib(6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到计算斐波那契数列时，会有很多重复计算。如果使用lru_cache，性能会显著改善："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.00000053s] fib(0) -> 0\n",
      "[0.00000058s] fib(1) -> 1\n",
      "[0.00022043s] fib(2) -> 1\n",
      "[0.00000070s] fib(3) -> 2\n",
      "[0.00026606s] fib(4) -> 3\n",
      "[0.00000058s] fib(5) -> 5\n",
      "[0.00032038s] fib(6) -> 8\n",
      "8\n"
     ]
    }
   ],
   "source": [
    "@functools.lru_cache()\n",
    "@clock\n",
    "def fib(n):\n",
    "    if n < 2:\n",
    "        return n\n",
    "    return fib(n-2) + fib(n-1)\n",
    "\n",
    "print(fib(6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，使用lru_cache后，每个参数只计算了一次。\n",
    "\n",
    "lru_cache有2个参数，maxsize和typed，默认值为128和False：`functools.lru_cache(maxsize=128, typed=False)`\n",
    "\n",
    "maxsize表示缓存的最大容量，超过配置的容量后最久未使用的缓存会被清理掉。typed表示是否区分参数类型，默认不区分，比如函数参数1和1.0会记录为同一个缓存项。\n",
    "\n",
    "lru_cache内部使用散列表存储，所以被lru_cache装饰的函数，它的所有参数都必须是可散列的。另外散列还会被组织成双向链表，这样可以快速更新和删除缓存。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
